<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Benchmark</title>
    <script src="../dist/zrender.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/zrender@4.2.0/dist/zrender.js"></script> -->
    <script src="lib/stats.js"></script>
    <script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body style="margin:0px;">


    <div style="text-align: right">
        <input type="button" onclick="setParam(['with-text'], '')" value="No Text"/>
        <input type="button" onclick="setParam(['with-text'], 'with-text')" value="Has Text"/>
        <input type="button" onclick="setParam(['canvas', 'svg'], 'canvas')" value="Canvas"/>
        <input type="button" onclick="setParam(['canvas', 'svg'], 'svg')" value="SVG"/>
    </div>
    <div id="main" style="width:600px;height:600px;"></div>

    <script type="text/javascript">

    function setParam(cleanList, newVal) {
        var params = location.search.split('&');
        for (var i = params.length - 1; i >= 0; i--) {
            for (var j = 0; j < cleanList.length; j++) {
                if (params[i] === cleanList[j]) {
                    params.splice(i, 1);
                }
            }
        }
        newVal && params.push(newVal);
        params.sort();
        location.search = params.join('&');
    }

    function hasParam(val) {
        var params = location.search.split('&');
        for (var i = params.length - 1; i >= 0; i--) {
            if (params[i] === val) {
                return true;
            }
        }
        return false;
    }


    var withText = hasParam('with-text');
    var renderMode = hasParam('svg') ? 'svg' : 'canvas';

var zr = zrender.init(document.getElementById('main'), {
    renderer: 'svg-ssr'
});

var stats = new Stats();
document.body.appendChild(stats.dom);

option = {
    series: []
};

/**
 * Given a set of points, lay them out in a phyllotaxis layout.
 * Mutates the `points` passed in by updating the x and y values.
 *
 * @param {Object[]} points The array of points to update. Will get `x` and `y` set.
 * @param {Number} pointWidth The size in pixels of the point's width. Should also include margin.
 * @param {Number} xOffset The x offset to apply to all points
 * @param {Number} yOffset The y offset to apply to all points
 *
 * @return {Object[]} points with modified x and y
 */
function phyllotaxisLayout(points, pointWidth, xOffset = 0, yOffset = 0, iOffset = 0) {
    // theta determines the spiral of the layout
    var theta = Math.PI * (3 - Math.sqrt(5));

    var pointRadius = pointWidth / 2;

    points.forEach((point, i) => {
        var index = (i + iOffset) % points.length;
        var phylloX = pointRadius * Math.sqrt(index) * Math.cos(index * theta);
        var phylloY = pointRadius * Math.sqrt(index) * Math.sin(index * theta);

        point.x = xOffset + phylloX - pointRadius;
        point.y = yOffset + phylloY - pointRadius;
    });

    return points;
}

/**
 * Given a set of points, lay them out in a grid.
 * Mutates the `points` passed in by updating the x and y values.
 *
 * @param {Object[]} points The array of points to update. Will get `x` and `y` set.
 * @param {Number} pointWidth The size in pixels of the point's width. Should also include margin.
 * @param {Number} gridWidth The width of the grid of points
 *
 * @return {Object[]} points with modified x and y
 */
function gridLayout(points, pointWidth, gridWidth) {
    var pointHeight = pointWidth;
    var pointsPerRow = Math.floor(gridWidth / pointWidth);
    var numRows = points.length / pointsPerRow;

    points.forEach((point, i) => {
        point.x = pointWidth * (i % pointsPerRow);
        point.y = pointHeight * Math.floor(i / pointsPerRow);
    });

    return points;
}

/**
 * Given a set of points, lay them out randomly.
 * Mutates the `points` passed in by updating the x and y values.
 *
 * @param {Object[]} points The array of points to update. Will get `x` and `y` set.
 * @param {Number} pointWidth The size in pixels of the point's width. Should also include margin.
 * @param {Number} width The width of the area to place them in
 * @param {Number} height The height of the area to place them in
 *
 * @return {Object[]} points with modified x and y
 */
function randomLayout(points, pointWidth, width, height) {
    points.forEach((point, i) => {
        point.x = Math.random() * (width - pointWidth);
        point.y = Math.random() * (height - pointWidth);
    });

    return points;
}

/**
 * Given a set of points, lay them out in a sine wave.
 * Mutates the `points` passed in by updating the x and y values.
 *
 * @param {Object[]} points The array of points to update. Will get `x` and `y` set.
 * @param {Number} pointWidth The size in pixels of the point's width. Should also include margin.
 * @param {Number} width The width of the area to place them in
 * @param {Number} height The height of the area to place them in
 *
 * @return {Object[]} points with modified x and y
 */
function sineLayout(points, pointWidth, width, height) {
    var amplitude = 0.3 * (height / 2);
    var yOffset = height / 2;
    var periods = 3;
    var yScale = d3.scaleLinear()
        .domain([0, points.length - 1])
        .range([0, periods * 2 * Math.PI]);

    points.forEach((point, i) => {
        point.x = (i / points.length) * (width - pointWidth);
        point.y = amplitude * Math.sin(yScale(i)) + yOffset;
    });

    return points;
}

/**
 * Given a set of points, lay them out in a spiral.
 * Mutates the `points` passed in by updating the x and y values.
 *
 * @param {Object[]} points The array of points to update. Will get `x` and `y` set.
 * @param {Number} pointWidth The size in pixels of the point's width. Should also include margin.
 * @param {Number} width The width of the area to place them in
 * @param {Number} height The height of the area to place them in
 *
 * @return {Object[]} points with modified x and y
 */
function spiralLayout(points, pointWidth, width, height) {
    var amplitude = 0.3 * (height / 2);
    var xOffset = width / 2;
    var yOffset = height / 2;
    var periods = 20;

    var rScale = d3.scaleLinear()
        .domain([0, points.length - 1])
        .range([0, Math.min(width / 2, height / 2) - pointWidth]);

    var thetaScale = d3.scaleLinear()
        .domain([0, points.length - 1])
        .range([0, periods * 2 * Math.PI]);

    points.forEach((point, i) => {
        point.x = rScale(i) * Math.cos(thetaScale(i)) + xOffset
        point.y = rScale(i) * Math.sin(thetaScale(i)) + yOffset;
    });

    return points;
}




/**
 * Generate an object array of `numPoints` length with unique IDs
 * and assigned colors
 */
function createPoints(numPoints, pointWidth, width, height) {
    var colorScale = d3.scaleSequential(d3.interpolateViridis)
        .domain([numPoints - 1, 0]);

    var points = d3.range(numPoints).map(id => ({
        id,
        color: colorScale(id),
    }));

    return randomLayout(points, pointWidth, width, height);
}

// canvas settings
var width = zr.getWidth();
var height = zr.getHeight();

// point settings
var numPoints = withText ? 2000 : 10000;
var pointWidth = 3;
var pointMargin = 3;

// animation settings
var duration = 1500;
var ease = d3.easeCubic;
var timer;
var currLayout = 0;

// create set of points
var points = createPoints(numPoints, pointWidth, width, height);

// wrap layout helpers so they only take points as an argument
var toGrid = (points) => gridLayout(points,
    pointWidth + pointMargin, width);
var toSine = (points) => sineLayout(points,
    pointWidth + pointMargin, width, height);
var toSpiral = (points) => spiralLayout(points,
    pointWidth + pointMargin, width, height);
var toPhyllotaxis = (points) => phyllotaxisLayout(points,
    pointWidth + pointMargin, width / 2, height / 2);

// store the layouts in an array to sequence through
var layouts = [toSine, toPhyllotaxis, toSpiral, toPhyllotaxis, toGrid];

// animate the points to a given layout
function animate(rects, layout) {
    // store the source position
    points.forEach(point => {
        point.sx = point.x;
        point.sy = point.y;
    });

    // get destination x and y position on each point
    layout(points);

    // store the destination position
    points.forEach(point => {
        point.tx = point.x;
        point.ty = point.y;
    });

    timer = d3.timer((elapsed) => {
        stats.begin();
        // compute how far through the animation we are (0 to 1)
        var t = Math.min(1, ease(elapsed / duration));

        // update point positions (interpolate between source and target)
        points.forEach(point => {
            point.x = point.sx * (1 - t) + point.tx * t;
            point.y = point.sy * (1 - t) + point.ty * t;
        });

        // update what is drawn on screen
        for (let i = 0; i < rects.length; i++) {
            var rect = rects[i];
            var point = points[i];
            rect.shape.x = point.x;
            rect.shape.y = point.y;
            rect.dirtyShape ? rect.dirtyShape() : rect.dirty(true);
        }

        // if this animation is over
        if (t === 1) {
            // stop this timer for this layout and start a new one
            timer.stop();

            // update to use next layout
            currLayout = (currLayout + 1) % layouts.length;

            // start animation for next layout
            animate(rects, layouts[currLayout]);
        }

        stats.end();
    });
}
zr.clear();
console.time('init');
var rects = [];
for (var i = 0; i < points.length; i++) {
    var pt = points[i];
    var rect = new zrender.Rect({
        shape: {
            width: pointWidth,
            height: pointWidth
        },
        style: {
            fill: pt.color
            // fill: 'green'
        },
        // autoBatch: true,
        silent: true
    });
    zr.add(rect);
    rects.push(rect);
}
console.timeEnd('init');
// start off as a grid
toGrid(points);

setTimeout(function () {
    // start the animation
    animate(rects, layouts[currLayout]);
}, 1000);
    </script>

</body>
</html>